home *** CD-ROM | disk | FTP | other *** search
/ Almathera Ten Pack 3: CDPD 3 / Almathera Ten on Ten - Disc 3: CDPD3.iso / scope / 026-050 / scopedisk28 / mrprint / mrprint.c < prev    next >
C/C++ Source or Header  |  1995-03-18  |  19KB  |  789 lines

  1. /* :ts=4 */
  2. /*
  3.    MRPrint:      detabbing text file printer for the Amiga
  4.    Author:        Mark Rinfret (Usenet: mrr@amanpt1; Bix: markr)
  5.  
  6.       I am offering this to the Amiga user community without restrictions.
  7.       If you make improvements, please re-release with source.    Enjoy!
  8.  
  9.  
  10.    This program will print text files containing embedded tabs and
  11.    form feeds.  Though the default tab setting is 4, the user may
  12.    override this to some other value as necessary.  MRPrint will also
  13.    optionally output a page header containing the filename, current
  14.    date and time, line number and page number.  MRPrint supports variable
  15.    margins and will enforce them.  Line numbers will be printed if
  16.    requested.  Note that by default, MRPrint prints to PRT:.  If you wish
  17.    to redirect output, be sure to use the "-s" option.
  18.  
  19.       Usage:  pr [-l] [-n#] [-t#] [-h] [file1] file2] ...
  20.       options:
  21.             -h        do not print a page header
  22.             -l        print with line numbers
  23.             -L#     set left margin to #
  24.             -n#     print # lines per page 
  25.             -R#     set right margin to #
  26.             -s        print to standard output
  27.             -t#     set tab to # spaces (default 4)
  28.  
  29.       Handles ARP wildcarding.
  30.  
  31.       05/13/88 -MRR- Yeah, I know - version 3.0 didn't last very long.
  32.                      I observed the output with the -s option and decided
  33.                      that the single character I/O I was doing was very
  34.                      unacceptable.  This version buffers both input and
  35.                      output.
  36.  
  37.       05/12/88 -MRR- THIS PROGRAM HAS BEEN ARPIFIED!  What the hell, 
  38.                      I've been wanting to dig into ARP for quite a
  39.                      while.  Now that I have V1.1 of ARP, V3.6 of Manx
  40.                      and a day off, this was as good a program as any
  41.                      to do some exploring.
  42. */
  43.  
  44. #define AMIGA
  45. /* #define DEBUG */
  46.  
  47. #include <stdio.h>
  48. #include <ctype.h>
  49. #include <libraries/arpbase.h>
  50. #include <arpfunctions.h>
  51. #include <functions.h>
  52.  
  53. #define VERSION "pr version 3.1, 05/13/88 (requires ARP V1.1 or higher)"
  54.  
  55. #define INBUFSIZE    4096L            /* input buffer size */
  56. #define MAXLINE     256
  57. #define OUTBUFSIZE    2048L            /* output buffer size */
  58. #define yes         1
  59. #define no            0
  60. #define SizeOf(x)    ((ULONG) sizeof(x))
  61.  
  62. /* An extended AnchorPath structure to enable full pathnames to be
  63.  * generated by FindFirst, FindNext.
  64.  */
  65.  
  66. struct UserAnchor {
  67.     struct AnchorPath    ua_AP;
  68.     BYTE                 moreMem[255];
  69.     };
  70.  
  71. char                 *FGets();        /* AmigaDOS/ARP compatible version. */
  72. char                   *NextFile();
  73. void                PutNumber();
  74. void                PutOneChar();
  75. void                PutString();
  76.  
  77. unsigned            abort;            /* Set by CTRL-C, really unnecessary. */
  78. struct UserAnchor    *anchor;        /* Used by FindFirst, FindNext */
  79. struct DateTime        *dateAndTime;    /* Go ahead - take a wild guess. */
  80. char                dateStr[20], timeStr[20];
  81. unsigned            doLineNumbers = no;
  82. unsigned            endOfInput;
  83. BPTR                f;                /* The current input file (handle) */
  84. char                   *fileName;        /* The name of the input file. */
  85. unsigned            forcePage;        /* Set by \f. */
  86. unsigned            headers = yes;    /* Controls page header generation. */
  87. UBYTE                *inBuf,*inBufPtr; /* Input buffer, sliding pointer */
  88. unsigned            inBufCount, inBufLength;
  89. unsigned            leftMargin = 5;
  90. unsigned            lineNumber;
  91. unsigned            linesPerPage = 55;
  92. UBYTE                 *outBuf, *outBufPtr;/* Output buffer, sliding pointer */
  93. unsigned            outBufLength;    /* Length of output buffer. */
  94. unsigned            pageNumber;
  95. BPTR                printer;        /* Output device/file handle. */
  96. static char            *prtname =     "PRT:";
  97. LONG                result;            /* Result of wildcard processing. */
  98. unsigned            rightMargin = 85;
  99. unsigned            srcLine;        /* Current source file line number. */
  100. unsigned            tabSpace = 4;    /* How many spaces 1 tab equals. */
  101. unsigned            tabStops[MAXLINE]; /* Computed tab stops. */
  102. unsigned            useRequester = no; /* Get filenames with requester? */
  103. unsigned            useStdOut = no; /* Print to standard output? */
  104. unsigned            xargc;            /* arg count after option processing */
  105. char                  **xargv;        /* arg vector after option processing */
  106.  
  107.  
  108.  
  109. /* This is where all goodness begins.  Actually, I'm not too happy with
  110.  * the size of the main program.  It ought to be broken up (or down :-).
  111.  */
  112. main (argc, argv)
  113.     int argc; char *argv[];
  114. {
  115.     unsigned    i;
  116.     char           *s;
  117.  
  118.     if (argc) {                        /* zero if started from workbench */
  119.         ++argv;                        /* skip over program name arg */
  120.         --argc;
  121.  
  122.      /* ..process switches.. */
  123.         for (; *(s = *(argv)) == '-'; ++argv, --argc) {
  124.             while (*++s)
  125.                 switch (*s) {
  126.                     case '?': 
  127.                         Usage();
  128.  
  129.                     case 'l': 
  130.                         doLineNumbers = yes;
  131.                         break;
  132.                     case 'L': 
  133.                         if ((leftMargin = Atol (s + 1)) <= 0) {
  134.                             Abort("Bad left margin ", (long) leftMargin);
  135.                         }
  136.                         goto next_arg;    /* Oh my gawd!  A GOTO! */
  137.                     case 'n': 
  138.                         linesPerPage = Atol (s + 1);
  139.                         goto next_arg;    /* Oh no!  A nuther one! */
  140.                         break;
  141.                     case 'R': 
  142.                         if ((rightMargin = Atol (s + 1)) <= 0 ||
  143.                                 rightMargin > MAXLINE) {
  144.                             Abort("Bad right margin ", (long) rightMargin);
  145.                         }
  146.                         goto next_arg;    /* It's a bloody epidemic! */
  147.                     case 's': 
  148.                         useStdOut = yes;
  149.                         break;
  150.                     case 't': 
  151.                         if ((tabSpace = Atol (s + 1)) <= 0) {
  152.                             Abort("Bad tab specification ", (long) tabSpace);
  153.                         }
  154.                         goto next_arg;    /* This is disgusting! */
  155.                     case 'h': 
  156.                         headers = no;
  157.                         break;
  158.                     case 'v':
  159.                         Printf("\n%s\n", VERSION);
  160.                         break;
  161.                     default: 
  162.                         Usage();
  163.                 }
  164.         /* Gag!  A label! There must be some goto's sneakin' around... */ 
  165.         next_arg:      ;
  166.             }
  167.         }
  168.  
  169.  /* Check a few argument combinations. */
  170.  
  171.     if (leftMargin >= rightMargin) {
  172.         Abort("Left margin >= right margin?  Ha ha!", 0L);
  173.     }
  174.  
  175.     if (doLineNumbers)
  176.         leftMargin = 5;            /* No margins with numbering but numbers
  177.                                    use 5 columns. */
  178.  
  179.  
  180.     SetTabs();                    /* Initialize tab settings. */
  181.  
  182.     /* Allocate input and output buffers. */
  183.  
  184.     inBuf = ArpAlloc(INBUFSIZE);
  185.     if (inBuf == NULL)
  186.         Abort("No memory for input buffer!", INBUFSIZE);
  187.  
  188.     outBuf = ArpAlloc(OUTBUFSIZE);
  189.     if (outBuf == NULL)
  190.         Abort("No memory for output buffer!", OUTBUFSIZE);
  191.  
  192.      /* Get the date and time; we might need it. */
  193.  
  194.     dateAndTime = (struct DateTime *) ArpAlloc(SizeOf(*dateAndTime));
  195.     if (dateAndTime == NULL) {
  196.         Abort("No memory!", SizeOf(*dateAndTime));
  197.     }
  198.  
  199.     DateStamp(dateAndTime);
  200.     dateAndTime->dat_Format = FORMAT_USA;
  201.     dateAndTime->dat_StrDate = dateStr;
  202.     dateAndTime->dat_StrTime = timeStr;
  203.     StamptoStr(dateAndTime);
  204.  
  205.     if (useStdOut)
  206.         printer = (BPTR) Output();
  207.     else
  208.         if ((printer = ArpOpen(prtname, MODE_NEWFILE)) == NULL) {
  209.             Abort("Failed to open printer ", IoErr());
  210.         }
  211.  
  212.      /* Process files. */
  213.  
  214.     xargv = argv;
  215.     if ((xargc = argc) == 0)        /* If no filename args, use requester. */
  216.         useRequester = yes;
  217.     else {
  218.         if ((anchor = (struct UserAnchor *)
  219.             ArpAlloc(SizeOf(*anchor))) == NULL) {
  220.             Abort("No memory!", SizeOf(*anchor));
  221.         }
  222.         anchor->ua_AP.ap_Length = 255;    /* Want full path built. */
  223.         anchor->ua_AP.ap_BreakBits |= 
  224.             (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
  225.  
  226.         result = ERROR_NO_MORE_ENTRIES;
  227.     }
  228.  
  229.     while (!abort && (fileName = NextFile()) ) {
  230.         if ((f = (BPTR) Open(fileName, MODE_OLDFILE)) != NULL) {
  231.             PrintFile();
  232.             Close(f);
  233.             f = NULL;
  234.         }
  235.         else
  236.             Printf("\n*** MRPrint: Can't open %s for printing ***\n", 
  237.                    fileName);
  238.     }
  239. }
  240.  
  241. /* Abort the program.
  242.  * Called with:
  243.  *        desc:        descriptive text
  244.  *        code:        error code (printed if non-zero)
  245.  * Returns:
  246.  *        to the system, where else?!
  247.  */
  248.  
  249. Abort(desc, code)
  250.     char *desc; long code;
  251. {
  252.     Printf("\n*** MRPrint aborting: %s", desc);
  253.     if (code)
  254.         Printf(" (%ld) ", code);
  255.     Puts(" ***");
  256.     if (f) Close(f);                /* File open? Close it. */
  257.     ArpExit(20L, 0L);
  258. }
  259.  
  260.  
  261. /* Print one file. */
  262.  
  263. PrintFile() 
  264. {
  265.     char    line[MAXLINE];
  266.  
  267.     forcePage = pageNumber = srcLine = 0;
  268.  
  269.     lineNumber = linesPerPage;
  270.  
  271.     inBufPtr = inBuf;
  272.     inBufLength = 0;
  273.     inBufCount = 0;
  274.     outBufPtr = outBuf;
  275.     outBufLength = 0;
  276.     endOfInput = no;
  277.  
  278.     while (FGets (line, MAXLINE - 1, f) != NULL && !abort ) {
  279.         ++srcLine;                    /* count input lines */
  280.  
  281. /* Note that top-of-form detection was a rather kludgy addition.  It only
  282.  * works if the first character in the line is a ^L.
  283.  */
  284.         if (*line == '\f') {
  285.             *line = ' ';            /* replace embedded ^L with blank */
  286.             lineNumber = linesPerPage;/* force new page */
  287.         }
  288.  
  289.         if (lineNumber >= linesPerPage)
  290.             Header();
  291.         DeTab(line);                /* ..output detabbed line.. */
  292.  
  293.     }
  294.  
  295.     PutOneChar('\f');                 /* ..form-feed after last page.. */
  296.     FlushBuffer();
  297. }
  298.  
  299. /* An attempt has been made to print a line past the right margin.
  300.  * Crash the user's system and melt his...naw, force a new line and 
  301.  * output a new left margin.  Also, if the page line count has been 
  302.  * exceeded, start a new page.
  303.  */
  304.  
  305. BreakLine() 
  306. {
  307.     PutOneChar('\n');
  308.     if (++lineNumber > linesPerPage)
  309.         Header();
  310.     DoLeftMargin();
  311. }
  312.  
  313. /* Output a dashed line according to an obscure formula derived through
  314.  * intense empirical analysis while listening to the tune
  315.  *
  316.  * "Camptown ladies sing this song, DoDash, DoDash..."
  317.  */
  318.  
  319. DoDash()
  320. {
  321.     PutMany(' ', leftMargin);
  322.     PutMany('-', rightMargin - leftMargin - 5);
  323.     PutOneChar('\n');
  324. }
  325.  
  326. /* Output spaces for the left margin, or a source line number, whatever
  327.  * tickles the user's fanny....fancy!
  328.  */
  329.  
  330. DoLeftMargin() 
  331. {
  332.     unsigned    i;
  333.  
  334.     if (doLineNumbers) {
  335.         PutNumber(srcLine, 4);
  336.         PutOneChar(' ');
  337.     }
  338.     else
  339.         PutMany(' ', leftMargin);
  340. }
  341.  
  342. /* Noch ein bier, bitte, mit der grosse kopf.
  343.  * That's Deutch for "Put a header on this page, please!".
  344.  */
  345.  
  346. Header() 
  347. {
  348.     int     i;
  349.  
  350.     if (++pageNumber != 1) {
  351.         PutOneChar('\f');            /* Eject if not first page. */
  352.         PutOneChar('\n');
  353.     }
  354.     if (headers) {
  355.         DoDash();
  356.  
  357. /* Note: there's room for improvement here.  A fancier algorithm would
  358.  * attempt to distribute this information evenly over the current page
  359.  * width.  A less lazy programmer would have written the fancier algorithm.
  360.  */
  361.         DoLeftMargin();
  362.         PutString(fileName);
  363.         PutMany(' ', 2);
  364.         PutString(dateStr);
  365.         PutMany(' ', 2);
  366.         PutString(timeStr);
  367.         PutString("  Page ");
  368.         PutNumber(pageNumber, 0);
  369.         PutString("  Line ");
  370.         PutNumber(srcLine, 0);
  371.         PutOneChar('\n');
  372.  
  373.         DoDash();
  374.  
  375.         PutString("\n");
  376.     }
  377.     lineNumber = 0;
  378. }
  379.  
  380.  
  381. /* Replace embedded tab characters with the appropriate number of spaces,
  382.  * outputting the results to the output device/file.
  383.  * Called with:
  384.  *        line:        string on which to do replacements
  385.  * Returns:
  386.  *        eventually :-)
  387.  */
  388.  
  389. DeTab(line)                            /* DeTab is not as good as DePepsi. */
  390.     char   *line;
  391. {
  392.     int     eol = 0, i, col;
  393.  
  394.     DoLeftMargin();
  395.     col = leftMargin;
  396.  
  397.  /* Note: line[] has a terminating '\n' from fgets()...except if 
  398.   * the input line length exceeded MAXLINE. 
  399.   */
  400.     for (i = 0; i < strlen (line); ++i)
  401.         if (line[i] == '\t') {        /* ..tab.. */
  402.             do {
  403.                 if (col == rightMargin) {
  404.                     BreakLine();
  405.                     break;
  406.                 }
  407.                 PutOneChar(' ');
  408.                 ++col;
  409.             } while (!tabStops[col]);
  410.         }
  411.  
  412.         else {
  413.             if (line[i] == '\n')
  414.                 ++eol;
  415.             else
  416.                 if (col == rightMargin)
  417.                     BreakLine();
  418.             PutOneChar(line[i]);
  419.             ++col;
  420.         }
  421.     if (!eol)
  422.         PutOneChar('\n');        /* no end of line? */
  423.     ++lineNumber;
  424. }
  425.  
  426. /* Initialize the tab settings for this file. */
  427.  
  428. SetTabs() 
  429. {
  430.     int     i;
  431.  
  432.     for (i = 0; i < MAXLINE; ++i)
  433.         tabStops[i] = (i % tabSpace == 1);
  434. }
  435.  
  436.  
  437. /* Display correct program Usage, then exit. */
  438.  
  439. Usage() 
  440. {
  441.     register unsigned   i;
  442.     register char       *s;
  443.  
  444.     static char *usageText[] = {
  445.         "Usage:  pr [-l] [-n#] [-t#] [-h] [-v] [file1] file2] ...",
  446.         "\toptions:",
  447.         "\t\t-h      do not print page headers",
  448.         "\t\t-l      print with line numbers",
  449.         "\t\t-L#     set left margin to #",
  450.         "\t\t-n#     print # lines per page",
  451.         "\t\t-R#     set right margin to #",
  452.         "\t\t-s      print to standard output instead of PRT:",
  453.         "\t\t-t#     set tab to # spaces (default 4)",
  454.         "\t\t-v      display program version number",
  455.         "ARP wildcarding is supported.",
  456.         (char *) NULL                /* last entry MUST be NULL */
  457.     };
  458.  
  459.     for (i = 0; s = usageText[i]; ++i)
  460.         Puts(s);
  461.  
  462.     ArpExit(20L, 0L);
  463. }
  464.  
  465. /* Get the next file name, either from the argument list or via a
  466.  * requester.
  467.  */
  468.  
  469. char*
  470. NextFile() 
  471. {
  472. #define NUMBEROFNAMES    10L
  473.  
  474.     static struct FileRequester request;
  475.     static char dName[DSIZE*NUMBEROFNAMES+1] = "";
  476.     static char fName[FCHARS+1] = "";
  477.  
  478.     struct FileLock *lock;
  479.  
  480.     if (useRequester) {
  481.         if (request.fr_File == NULL) {
  482.             request.fr_File = fName;
  483.  
  484.             /* To get the current directory path, get a lock on it, then
  485.              * use PathName to convert it to a full path.
  486.              */
  487.             lock = Lock("", ACCESS_READ);
  488.             PathName(lock, dName, NUMBEROFNAMES);
  489.             UnLock(lock);
  490.             request.fr_Dir = dName;
  491.             request.fr_Hail = "Select file to print:";
  492.         }
  493.         return FileRequest(&request);
  494.     }
  495.  
  496.     /* Note: result is initialized to ERROR_NO_MORE_ENTRIES prior to
  497.      * calling this routine for the first time.
  498.      */
  499.  
  500.     while ((result == 0) || (result == ERROR_NO_MORE_ENTRIES)) {
  501.  
  502.         if (result == 0) {                /* Working a pattern? */
  503.             if ((result = FindNext(anchor)) == 0L) {
  504.                 if (SkipDirEntry(anchor)) continue;
  505.                 break;
  506.             }
  507.         }
  508.  
  509.         if (result == ERROR_NO_MORE_ENTRIES) {
  510.             if (xargc <= 0) {
  511.                 result = -1;
  512.                 break;
  513.             }
  514.             result = FindFirst(*xargv, anchor);
  515.             ++xargv;                    /* Advance arg list pointer. */
  516.             --xargc;                    /* One less arg to process. */
  517.             if (result == 0) {
  518.                 if (SkipDirEntry(anchor)) continue;
  519.                 break;
  520.             }
  521.         }
  522.  
  523.         /* Only one error code is acceptable: */
  524.  
  525.         if (result && (result != ERROR_NO_MORE_ENTRIES)) {
  526.             Printf("\n*** MRPrint I/O error %ld on pattern %s ***\n",
  527.                    result, *xargv);
  528.             result = 0;                /* Allow another pass. */
  529.         }
  530.     }
  531.  
  532.     /* Return filename or NULL, depending upon result. */
  533.     return (result == 0 ? (char *) &anchor->ua_AP.ap_Buf : NULL);
  534. }
  535.  
  536. /* Read one line (including newline) from the input file. 
  537.  * Called with:
  538.  *        line:        string to receive text
  539.  *        maxLength:    maximum length of string
  540.  *        f:            AmigaDOS file handle bee pointer (BPTR, ya' know).
  541.  */
  542.  
  543. char *
  544. FGets(line, maxLength, f)
  545.     char *line; int maxLength; BPTR f;
  546. {
  547.     char     *buf = line;
  548.     int        c;
  549.     int        lineLength = 0;
  550.  
  551.     if (abort = CheckAbort(NULL)) {
  552.         PutString("\n^C\f");
  553.         Abort("^C", 0L);
  554.     }
  555.  
  556.     while (lineLength < maxLength) {
  557.         if ( ( c = GetOneChar(f) ) < 0 ) break;
  558.         ++lineLength;
  559.         if ((*buf++ = c) == '\n') break;    /* Stop on end of line. */
  560.     }
  561.  
  562.     line[lineLength] = '\0';
  563.  
  564.     if (c < -1) {
  565.  
  566.         /* Report the error to the printer and the console, but don't
  567.          * give up on the rest of the files.  I think they call that
  568.          * being user friendly.
  569.          */
  570.         c = -c;                                /* Invert the error code. */
  571.         Printf("*** I/O error on input %d ***\n", c);
  572.  
  573.         if (!useStdOut) {
  574.             PutString("*** Input I/O error");
  575.             PutNumber(c, 0);
  576.             PutString("***\n");
  577.         }
  578.  
  579.         lineLength = 0;
  580.     }
  581.  
  582.     return (lineLength == 0 ? NULL : line);
  583. }
  584.  
  585. /* Flush the printer (output) buffer (phew!). */
  586.  
  587. FlushBuffer()
  588. {
  589.     long    actualLength;
  590.     long    ioResult;
  591.  
  592.     if (outBufLength) {
  593.         actualLength = Write(printer, outBuf, (long) outBufLength);
  594.         if (actualLength != outBufLength) {
  595.             ioResult = IoErr();
  596.             Abort("Output error!", ioResult);
  597.         }
  598.     }
  599.     outBufPtr = outBuf;
  600.     outBufLength = 0;
  601. }
  602.  
  603. /* Get one character from the input stream.  If the input buffer is
  604.  * exhausted, attempt to get some more input.  If this is the first
  605.  * input buffer for this file, check the buffer for binary content.
  606.  * Called with:
  607.  *        f:        input file handle
  608.  * Returns:
  609.  *        character code (>= 0) or status (< 0, -1 => end of input)
  610.  */
  611.  
  612. int
  613. GetOneChar(f)
  614.     BPTR f;
  615. {
  616.     int        ioStatus;
  617.  
  618.     if (endOfInput)
  619.         return -1;
  620.  
  621.     if (inBufLength <= 0 ) {
  622.         inBufLength = Read(f, inBuf, INBUFSIZE);
  623.  
  624.         /* If this is the first buffer, test it for binary content.
  625.          * If the file is binary, skip it by setting the actualLength
  626.          * to zero (simulate end of file).
  627.          */
  628.         if ((++inBufCount == 1) && inBufLength > 0) {
  629.             if (SkipBinaryFile(anchor))
  630.                 inBufLength = 0;
  631.         }
  632.  
  633.         if (inBufLength <= 0) {
  634.             if (inBufLength == -1)
  635.                 ioStatus = -IoErr();
  636.             else {
  637.                 ioStatus = -1;
  638.                 endOfInput = yes;
  639.             }
  640.  
  641.             return ioStatus;
  642.         }
  643.         inBufPtr = inBuf;
  644.     }
  645.  
  646.     --inBufLength;
  647.     return *inBufPtr++;
  648. }
  649.  
  650. /* Put multiple copies of a character into the output buffer (repeat).
  651.  * Called with:
  652.  *        c:        character to be repeated
  653.  *        n:        number of copies
  654.  * Returns:
  655.  *        tired but satisfied
  656.  */
  657.  
  658. PutMany(c, n)
  659.     int    c, n;
  660.  
  661. {
  662.     for( ; n > 0; --n)
  663.         PutOneChar(c);
  664. }
  665.  
  666. /* Output a simple formatted unsigned number.
  667.  * Called with:
  668.  *        number:        value to be formatted
  669.  *        length:        number of digits desired (0 => doesn't matter)
  670.  */
  671. void
  672. PutNumber(number, length)
  673.     unsigned number, length;
  674. {
  675.     unsigned digitCount = 0, i;
  676.     char    digits[6];
  677.  
  678.     do {
  679.         digits[digitCount++] = (number % 9) + '0';
  680.         number /= 10;
  681.     } while (number);
  682.  
  683.     while (length > digitCount) {
  684.         PutOneChar(' ');
  685.         --length;
  686.     }
  687.  
  688.     do {
  689.         PutOneChar(digits[--digitCount]);
  690.     } while (digitCount);
  691. }
  692.  
  693. /* Output one character to the printer device/file.
  694.  * Called with:
  695.  *        c:            character to be output
  696.  * Returns:
  697.  *        nada
  698.  */
  699. void
  700. PutOneChar(c)
  701.     int        c;
  702. {
  703.     if (outBufLength >= OUTBUFSIZE) 
  704.         FlushBuffer();
  705.  
  706.     *outBufPtr++ = c;
  707.     ++outBufLength;
  708. }
  709.  
  710. /* Output a string to the printer device/file. 
  711.  * Called with:
  712.  *        s:        string to output
  713.  * Returns:
  714.  *        when it's done, of course!
  715.  */
  716. void
  717. PutString(s)
  718.     char    *s;
  719. {
  720.     register int    c;
  721.     register char    *s1;
  722.  
  723.     for (s1 = s; c = *s1; ++s1)
  724.         PutOneChar(c);
  725. }
  726.  
  727. /* Test the contents of the first buffer for binary data.  If the buffer
  728.  * is determined to have binary content, tell the user that we are
  729.  * skipping the file.  This allows the user to give a single wildcard
  730.  * specification without worrying about printing object, data and program
  731.  * files (assuming, of course, that binary data is detected within the
  732.  * first INBUFSIZE bytes of the file).
  733.  * Called with:
  734.  *        anchor:        pointer to UserAnchor structure describing the file
  735.  * Returns:
  736.  *        yes:        file contains binary
  737.  *        no:            file is text (we think)
  738.  */
  739.  
  740. int
  741. SkipBinaryFile(anchor)
  742.     struct UserAnchor *anchor;
  743. {
  744.     char *strchr();
  745.  
  746.     /* The following string describes binary characters that are considered
  747.      * to be "OK".  These are, from left to right:
  748.      *
  749.      * newline, form feed, tab, carriage return, backspace
  750.      *
  751.      */
  752.     static char     *okSpecial = "\n\f\t\015\010";
  753.     register UBYTE    c;
  754.     register int    i;
  755.     int             isBinary = no;
  756.  
  757.     for (i = 0; i < inBufLength; ++i)
  758.         if (((c = inBuf[i]) < ' ') || c > 0x7F) {
  759.             if (!strchr(okSpecial, c)) {
  760.                 isBinary = yes;
  761.                 break;
  762.             }
  763.         }
  764.     if (isBinary) {
  765.         Printf("\n*** MRPrint: skipping binary file %s ***\n", fileName);
  766.     }
  767.     return isBinary;
  768. }
  769.  
  770. /* Test the file described by the anchor parameter for "directoryness".
  771.  * If it's a directory, print a message that we're skipping it.
  772.  * Called with:
  773.  *        anchor:        file entry info returned by FindFirst, FindNext
  774.  * Returns:
  775.  *        yes:        file is a directory
  776.  *        no:            file is a file (astonishing, eh?)
  777.  */
  778. int
  779. SkipDirEntry(anchor)
  780.     struct UserAnchor *anchor;
  781. {
  782.     if (anchor->ua_AP.ap_Info.fib_DirEntryType >= 0) {
  783.         Printf("\n*** MRPrint: skipping directory %s ***\n",
  784.             &anchor->ua_AP.ap_Buf);
  785.         return yes;
  786.     }
  787.     return no;
  788. }
  789.